package consensus

import (
	"encoding/binary"
	"fmt"
	"sync"

	"fabric-orderer-tm/orderer/multichain"

	"github.com/golang/protobuf/proto"
	cb "github.com/hyperledger/fabric/protos/common"
	"github.com/tendermint/abci/types"
	"github.com/tendermint/tendermint/version"
)

const (
	AppHash = "ChariPBFTConsenter"
)

type echo struct {
	lock            sync.Mutex
	lastBlockHeight int64
	supports        map[string]multichain.ConsenterSupport
}

func (this *echo) NewSupport(channelId string, support multichain.ConsenterSupport) {
	this.lock.Lock()
	defer this.lock.Unlock()
	this.supports[channelId] = support
}

func (this *echo) getSupport(channelId string) multichain.ConsenterSupport {
	this.lock.Lock()
	defer this.lock.Unlock()
	return this.supports[channelId]
}

func (this *echo) Info(req types.RequestInfo) types.ResponseInfo {
	//	logger.Info("Info", req)

	if val, err := db.Get([]byte("lastBlockHeight"), nil); err == nil {
		//this.lastBlockHeight = binary.BigEndian.Uint64(val)
		this.lastBlockHeight = int64(binary.BigEndian.Uint64(val))
	}
	logger.Info("Info LastBlockHeight", this.lastBlockHeight)

	return types.ResponseInfo{
		Version:          version.Version,
		LastBlockHeight:  this.lastBlockHeight,
		LastBlockAppHash: []byte(AppHash),
	}
}

func (this *echo) SetOption(req types.RequestSetOption) (rs types.ResponseSetOption) {
	logger.Info("SetOption", req.Key, req.Value)
	return
}

func (this *echo) InitChain(req types.RequestInitChain) (rs types.ResponseInitChain) {
	logger.Info("InitChain", req)
	return
}

func (this *echo) CheckTx(tx []byte) (rs types.ResponseCheckTx) {
	//	logger.Info("CheckTx" /*, string(tx)*/)
	return
}

func (this *echo) BeginBlock(req types.RequestBeginBlock) (rs types.ResponseBeginBlock) {
	//	logger.Info("BeginBlock", req)
	return
}

func (this *echo) DeliverTx(tx []byte) types.ResponseDeliverTx {
	channelId := string(tx[1 : tx[0]+1])
	//	logger.Info("DeliverTx", channelId /*, string(tx[tx[0]+1:])*/)

	env := new(cb.Envelope)
	if err := proto.Unmarshal(tx[tx[0]+1:], env); err != nil {
		logger.Error(err)
		//return types.NewResult(types.CodeType_InternalError, nil, err.Error())
		return types.ResponseDeliverTx{
			Code: 1,
			Log:  fmt.Sprintf(err.Error()),
			Data: []byte(""),
		}
	}

	support := this.getSupport(channelId)
	if support == nil {
		//return types.NewResult(types.CodeType_InternalError, nil, fmt.Sprint("not found channelID ", channelId, " 's support"))
		return types.ResponseDeliverTx{
			Code: 1,
			Log:  fmt.Sprintf("not found channelID ", channelId, " 's support"),
			Data: []byte(""),
		}
	}

	batches, committers, _, _ := support.BlockCutter().Ordered(env)
	if len(batches) > 0 {
		for i, batch := range batches {
			block := support.CreateNextBlock(batch)
			support.WriteBlock(block, committers[i], nil)
		}
	}
	return types.ResponseDeliverTx{
		Code: 0,
	}
}

func (this *echo) EndBlock(req types.RequestEndBlock) (rs types.ResponseEndBlock) {
	logger.Info("EndBlock", req.Height)
	this.lastBlockHeight = req.Height
	return
}

func (this *echo) Commit() types.ResponseCommit {
	//	logger.Info("Commit")

	this.lock.Lock()
	defer this.lock.Unlock()

	for _, support := range this.supports {
		batch, committers := support.BlockCutter().Cut()
		if len(batch) != 0 {
			block := support.CreateNextBlock(batch)
			support.WriteBlock(block, committers, nil)
		}
	}

	height := make([]byte, 8)
	binary.BigEndian.PutUint64(height, uint64(this.lastBlockHeight))
	if err := db.Put([]byte("lastBlockHeight"), height, nil); err != nil {
		logger.Error(err)
		return types.ResponseCommit{Data: nil}
	}
	//return types.NewResultOK([]byte(AppHash), "")
	return types.ResponseCommit{Data: []byte(AppHash)}
}

func (this *echo) Query(req types.RequestQuery) (rs types.ResponseQuery) {
	//	logger.Info("Query", req)
	return
}
